﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
namespace AirHockey
{
    public static class ObjParser
    {
        #region Members
        private static char[] splitChars = { ' ' };
        private static char[] deleteChars = { '\t', '\r', '\n', ' ' };
        private static List<Vector3> positions;
        private static List<Vector3> normals;
        private static List<Vector2> textureCoordinates;
        private static List<Face> faces;
        #endregion

        #region Public Methods
        public static Model ParseObjFile(string fileDirectory,string modelName)
        {
            return ReadModel(fileDirectory,modelName);
        }
        #endregion

        #region Private Methods
        private static string Clean(string data)
        {
            return data.Trim(deleteChars);
        }

        private static float FloatParseEx(String str)
        {
            if (str == string.Empty)
                return 0f;

            return float.Parse(str);
        }

        private static Color4 ReadColor(string data)
        {
            string[] n = data.Split(" ".ToCharArray(), 5);

            if (n.Length > 2)
            {
                float r = FloatParseEx(n[0]);
                float g = FloatParseEx(n[1]);
                float b = FloatParseEx(n[2]);
                float a = 1f;
                if (n.Length > 3)
                {
                    a = FloatParseEx(n[3]);
                }
                return new Color4(r, g, b, a);
            }

            return new Color4(1f, 1f, 1f, 1f);
        }

        private static Model ReadModel(string filePath,string modelName)
        {
            FileStream fs = new FileStream(filePath+"\\"+modelName+".obj", FileMode.Open, FileAccess.Read);
            StreamReader sr = new StreamReader(fs);
            Model model = new Model();

            positions = new List<Vector3>();
            normals = new List<Vector3>();
            textureCoordinates = new List<Vector2>();
            faces = new List<Face>();
           
            //string currentObjectName = string.Empty;
            //string currentGroupName = string.Empty;
            //string currentMapName = string.Empty;

            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();
                line = line.Trim(splitChars);
                line = line.Replace("  ", " ");

                string[] nubs = SplitOnDelim(line, " ", 2);

                if (nubs.Length > 0)
                {
                    if (nubs.Length > 1)
                    {
                        string code = nubs[0];
                        if (code == "o")
                        { }
                        //currentObjectName = nubs[1];
                        else if (code == "mtllib")
                            ReadMaterialFile(filePath + "\\" + nubs[1], model);
                        else if (code == "usemtl")
                            model.Texture = new Texture(nubs[1]);
                        else if (code == "usemap")
                        { }//currentMapName = nubs[1];
                        else if (code == "g")
                        { }    //currentGroupName = nubs[1];
                        else if (code == "v")
                            positions.Add(ReadV3D(nubs[1]));
                        else if (code == "vn")
                            normals.Add(ReadV3D(nubs[1]));
                        else if (code == "vt")
                            textureCoordinates.Add(ReadV2D(nubs[1]));
                        else if (code == "f")
                        {
                            faces.Add(ReadFace(nubs[1], model));   
                        }
                    }
                }
            }

            model.Mesh = new Mesh(faces);

            return model;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="model"></param>
        private static void ReadMaterialFile(string filePath,Model model)
        {
            FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            StreamReader sr = new StreamReader(fs);

            Material material = new Material();

            while (!sr.EndOfStream)
            {
                string line = sr.ReadLine();
                line = line.Trim(deleteChars);

                string[] nubs = SplitOnDelim(line, " ", 2);

                if (nubs.Length > 1)
                {
                    string code = nubs[0];

                    if (code == "newmtl")
                    {
                        material.MaterialName = nubs[1];
                    }
                    else if (material != null)
                    {
                        if (code == "map_Kd")
                        { }
                        else if (code == "Kd")
                            material.Kd = ReadColor(nubs[1]);
                        else if (code == "Ka")
                            material.Ka = ReadColor(nubs[1]);
                        else if (code == "Ks")
                            material.Ks = ReadColor(nubs[1]);
                        else if (code == "Ke")
                        { }
                        else if (code == "Ns")
                            material.Ns = FloatParseEx(nubs[1]);
                        else if (code == "Ni")
                            material.Ni = FloatParseEx(nubs[1]);
                        else if (code == "d" || code == "Tr")
                            material.D = FloatParseEx(nubs[1]);
                        else if (code == "Tf")
                        { }
                    }
                }
            }

            if (model != null)
            {
                model.AddMaterial(material);
            }
        }

        private static string[] SplitOnDelim(string data, string delim, int count)
        {
            return data.Split(delim.ToCharArray(), count);
        }

        private static string[] SplitOnDelim(string data, string delim)
        {
            return data.Split(delim.ToCharArray());
        }

        private static Vector2 ReadV2D(string data)
        {
            Vector2 v = new Vector2();
            string[] n = data.Split(splitChars);
            if (n.Length > 2)
                n[1] = (n[1].Split(splitChars))[0];

            if (n.Length > 1)
            {
                v.X = FloatParseEx(n[0]);
                v.Y = FloatParseEx(n[1]);
            }
            return v;
        }

        private static Vector3 ReadV3D(string data)
        {
            Vector3 v = new Vector3();
            string[] n = SplitOnDelim(data, " ", 3);
            if (n.Length > 2)
            {
                v.X = FloatParseEx(n[0]);
                v.Y = FloatParseEx(n[1]);
                v.Z = FloatParseEx(n[2]);
            }
            return v;
        }

        private static Face ReadFace(string data,Model model)
        {
              Face face = new Face();
              string[] nubs = SplitOnDelim(data, " ");
              foreach (string n in nubs)
              {
                  if (n.Contains("#"))
                  {
                      break;
                  }

                  string[] i;

                  if (n.Contains("//"))
                  {
                      string[] temp = SplitOnDelim(n, "//", 3);
                      i = new string[2];
                      i[0] = temp[0];
                      i[1] = temp[2];
                  }

                  else
                  {
                      i = SplitOnDelim(n, "/", 3);
                  }


                  if (i.Length > 0 && i[0] != string.Empty)
                  {
                      face.Vertices.Add(GetIndex(Int32.Parse(i[0]),positions));
                  }

                  if (i.Length > 1 && i[1] != string.Empty)
                  {
                      face.Uvs.Add(GetIndex(Int32.Parse(i[1]),textureCoordinates));
                  }

                  if (i.Length > 2 && i[2] != string.Empty)
                  {
                      face.Normals.Add(GetIndex(Int32.Parse(i[2]),normals));
                  }
             }

              return face;
        }

        private static Vector2 GetIndex(int index, List<Vector2> list)
        {
            //if (index <= 0 && index + list.Count >= 0)
            //    return list[list.Count - 1 + index];

            //if (index - 1 > list.Count)
            //    return list[list.Count - 1];

            return list[index - 1];
        }

        private static Vector3 GetIndex(int index, List<Vector3> list)
        {
            //if (index <= 0 && index + list.Count >= 0)
            //    return list[list.Count - 1 + index];

            //if (index - 1 > list.Count)
            //    return list[list.Count - 1];

            return list[index - 1];
        }
        #endregion
    }
}
